/*
 * SPDX-FileCopyrightText: 2023-2024 Andrew Gunnerson
 * SPDX-License-Identifier: GPL-3.0-only
 */

package com.sanji.audio_test.extension

import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import android.util.Log
import android.webkit.MimeTypeMap
import androidx.core.net.toFile
import androidx.documentfile.provider.DocumentFile
import java.io.File
import java.io.IOException

// 通用常量声明
private const val TAG = "DocumentFileExtensions"
private const val DOCUMENTS_UI_AUTHORITY  = "com.android.externalstorage.documents"

/** 
 * 获取 DocumentsProvider 支持的文件的内部 Context 对象
 * 
 * 通过反射访问 DocumentFile 的私有 mContext 字段，仅对 content:// URI 有效
 */
private val DocumentFile.context: Context?
    get() = when (uri.scheme) {
        ContentResolver.SCHEME_CONTENT -> {
            // 使用反射获取私有字段 mContext
            javaClass.getDeclaredField("mContext").apply {
                isAccessible = true
            }.get(this) as Context
        }
        else -> null
    }

/** 
 * 判断是否为树形 URI（可访问子文档的 URI）
 * 
 * 树形 URI 可以通过 buildChildDocumentsUriUsingTree 访问其子文档
 */
private val DocumentFile.isTree: Boolean
    get() = uri.scheme == ContentResolver.SCHEME_CONTENT && DocumentsContract.isTreeUri(uri)

/** 
 * 判断是否为本地文件（文件系统路径或 DocumentsUI 提供的文件）
 */
private val DocumentFile.isLocal: Boolean
    get() = uri.scheme == ContentResolver.SCHEME_FILE ||
            (uri.scheme == ContentResolver.SCHEME_CONTENT && uri.authority == DOCUMENTS_UI_AUTHORITY)

/**
 * 用于高效查询子文档的辅助函数
 * 
 * @param columns 需要查询的列名数组
 * @param block 处理查询结果的回调函数，接收 Cursor 和映射后的序列
 * @return R 泛型返回值
 */
private fun <R> DocumentFile.withChildrenWithColumns(
    columns: Array<String>,
    block: (Cursor, Sequence<Pair<DocumentFile, Cursor>>) -> R,
): R {
    // 确保是树形 URI
    require(isTree) { "Not a tree URI" }

    // 这些反射调用访问私有字段，但因为我们控制 androidx.documentfile 版本，所以是安全的
    val constructor = javaClass.getDeclaredConstructor(
        DocumentFile::class.java,
        Context::class.java,
        Uri::class.java,
    ).apply {
        isAccessible = true
    }

    // 构建查询子文档的 URI 并执行查询
    val cursor = context!!.contentResolver.query(
        DocumentsContract.buildChildDocumentsUriUsingTree(
            uri,
            DocumentsContract.getDocumentId(uri),
        ),
        columns + arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
        null, null, null,
    ) ?: throw IOException("Query returned null cursor: $uri: $columns")

    return cursor.use {
        // 获取文档 ID 列的索引
        val indexDocumentId =
            cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DOCUMENT_ID)

        // 执行回调函数处理结果
        block(cursor, cursor.asSequence().map {
            val documentId = it.getString(indexDocumentId)
            // 使用反射创建子文档的 DocumentFile 实例
            val child: DocumentFile = constructor.newInstance(
                this,
                context,
                DocumentsContract.buildDocumentUriUsingTree(uri, documentId),
            )

            Pair(child, it)
        })
    }
}

/**
 * 列出文件及其显示名称，对树形 URI 更快
 *
 * 对于非树形 URI，这等同于调用 [DocumentFile.listFiles]，然后对每个条目调用 [DocumentFile.getName]。
 * 对于树形 URI，这只执行一次查询到文档提供者。
 */
fun DocumentFile.listFilesWithNames(): List<Pair<DocumentFile, String>> {
    // 非树形 URI 使用标准方法
    if (!isTree) {
        return listFiles().map { Pair(it, it.name!!) }
    }

    return try {
        // 对树形 URI 使用优化的查询方法
        withChildrenWithColumns(arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME)) { c, sequence ->
            val indexDisplayName =
                c.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)

            sequence.map { it.first to it.second.getString(indexDisplayName) }.toList()
        }
    } catch (e: Exception) {
        Log.w(TAG, "Failed to query tree URI", e)
        emptyList()
    }
}

/**
 * 递归列出文件及其路径
 *
 * 使用 [listFilesWithNames] 以获得对树形 URI 更快的迭代速度
 */
fun DocumentFile.listFilesWithPathsRecursively(): List<Pair<DocumentFile, List<String>>> {
    val result = mutableListOf<Pair<DocumentFile, List<String>>>()

    // 递归遍历目录的内部函数
    fun recurse(dir: DocumentFile, path: List<String>) {
        for ((file, name) in dir.listFilesWithNames()) {
            val subPath = path + name

            result.add(Pair(file, subPath))

            // 如果是目录，递归处理
            if (file.isDirectory) {
                recurse(file, subPath)
            }
        }
    }

    recurse(this, emptyList())
    return result
}

/**
 * 类似于 [DocumentFile.findFile]，但对树形 URI 更快
 *
 * [DocumentFile.findFile] 会先查询文档 ID 列表，然后为每个文档分别执行查询以获取名称。
 * 在某些设备上这非常慢，而且没有必要，因为 [DocumentsContract.Document.COLUMN_DOCUMENT_ID] 和
 * [DocumentsContract.Document.COLUMN_DISPLAY_NAME] 可以同时查询。
 */
fun DocumentFile.findFileFast(displayName: String): DocumentFile? {
    // 非树形 URI 使用标准方法
    if (!isTree) {
        return findFile(displayName)
    }

    return try {
        // 对树形 URI 使用优化的查询方法
        withChildrenWithColumns(arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME)) { c, sequence ->
            val indexDisplayName =
                c.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)

            // 查找匹配显示名称的文件
            sequence.find { it.second.getString(indexDisplayName) == displayName }?.first
        }
    } catch (e: Exception) {
        Log.w(TAG, "Failed to query tree URI", e)
        null
    }
}

/** 
 * 类似于 [DocumentFile.findFileFast]，但接受嵌套路径
 * 
 * @param path 要查找的文件路径（分段列表）
 */
fun DocumentFile.findNestedFile(path: List<String>): DocumentFile? {
    var file = this
    // 逐级查找路径中的每个段
    for (segment in path) {
        file = file.findFileFast(segment) ?: return null
    }
    return file
}

/**
 * 查找子目录 [path] 或创建它（包括任何中间子目录）
 * 
 * @param path 要查找或创建的目录路径
 */
fun DocumentFile.findOrCreateDirectories(path: List<String>): DocumentFile? {
    var file = this

    // 如果是文件系统路径，确保根目录存在
    if (uri.scheme == ContentResolver.SCHEME_FILE) {
        uri.toFile().mkdirs()
    }

    // 逐级查找或创建目录
    for (segment in path) {
        file = file.findFileFast(segment)
            ?: file.createDirectory(segment)
            ?: return null
    }

    return file
}

/**
 * 类似于 [DocumentFile.createFile]，但为某些在旧版 Android 中不支持的 MIME 类型显式附加文件扩展名
 * 此特殊处理仅适用于本地文件
 * 
 * @param mimeType 文件的 MIME 类型
 * @param displayName 文件显示名称
 */
fun DocumentFile.createFileCompat(mimeType: String, displayName: String): DocumentFile? {
    val finalDisplayName = if (isLocal) {
        buildString {
            append(displayName)

            val mimeTypeMap = MimeTypeMap.getSingleton()

            // 如果 MIME 类型不在标准映射中，尝试获取兼容的扩展名
            if (!mimeTypeMap.hasMimeType(mimeType)) {
                val ext = mimeTypeMap.getExtensionFromMimeTypeCompat(mimeType)
                if (ext != null) {
                    append('.')
                    append(ext)
                }
            }
        }
    } else {
        displayName
    }

    return createFile(mimeType, finalDisplayName)
}

/**
 * 类似于 [DocumentFile.createFile]，但接受嵌套路径并自动创建中间子目录
 *
 * @param path 到文件的路径，不能为空。最后一个元素指定文件名，不应包含文件扩展名
 * @param mimeType 文件的 MIME 类型，决定文件扩展名
 */
fun DocumentFile.createNestedFile(mimeType: String, path: List<String>): DocumentFile? {
    require(path.isNotEmpty()) { "Path cannot be empty" }

    // 先查找或创建所有中间目录，然后创建文件
    return findOrCreateDirectories(path.dropLast(1))
        ?.createFileCompat(mimeType, path.last())
}

/** 
 * 类似于 [DocumentFile.renameTo]，但保留文件扩展名
 * 
 * @param displayName 新的显示名称（不包括扩展名）
 */
fun DocumentFile.renameToPreserveExt(displayName: String): Boolean {
    val newName = buildString {
        append(displayName)

        // 故意使用简单字符串操作，因为 MimeTypeMap 的 getExtensionFromMimeType()
        // 和 getMimeTypeFromExtension() 不一致
        // 例如: audio/mp4 -> m4a -> audio/mpeg -> mp3

        val ext = name!!.substringAfterLast('.', "")
        if (ext.isNotEmpty()) {
            append('.')
            append(ext)
        }
    }

    return renameTo(newName)
}

/** 
 * 获取 DocumentsProvider 支持的文件的标志位
 * 
 * 查询文档的标志位，用于判断文档支持的操作
 */
private val DocumentFile.flags: Int
    get() {
        require(uri.scheme == ContentResolver.SCHEME_CONTENT) {
            throw IllegalArgumentException("Not a DocumentsProvider URI")
        }

        context!!.contentResolver.query(
            uri,
            arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
            null, null, null,
        )?.use {
            if (it.moveToFirst()) {
                return it.getInt(0)
            }
        }

        return 0
    }

/**
 * 无法高效移动文件的异常
 * 
 * 当文件无法通过移动操作高效传输时抛出（需要复制+删除而不是直接移动）
 */
class NotEfficientlyMovableException(msg: String, cause: Throwable? = null)
    : IllegalArgumentException(msg, cause)

/**
 * 尝试将文件高效地移动到 [targetParent] 目录
 *
 * 文件可以高效移动（无需复制+删除）的条件：
 * * 源和目标是相同类型（file:// 或 DocumentsProvider 权限）
 * * 源和目标必须在同一挂载点上（对于 file://）
 * * 源文件具有已知的父级（通过 [DocumentFile.getParentFile]）
 * * 源文件声明了 [DocumentsContract.Document.FLAG_SUPPORTS_MOVE]
 *
 * 移动后，文件名保持不变。当 [targetParent] 已经有同名文件时会发生什么没有明确说明，
 * 因为 [DocumentsContract] API 对此场景没有提供保证。
 * 
 * @param targetParent 目标父目录
 * @return 移动后的 DocumentFile，如果移动失败则返回 null
 */
fun DocumentFile.moveToDirectory(targetParent: DocumentFile): DocumentFile? {
    // 检查 scheme 是否匹配
    if (uri.scheme != targetParent.uri.scheme) {
        throw NotEfficientlyMovableException("Source scheme (${uri.scheme}) != " +
                "target parent scheme (${targetParent.uri.scheme})")
    }

    when (uri.scheme) {
        ContentResolver.SCHEME_FILE -> {
            // 处理文件系统路径
            val sourceFile = uri.toFile()
            val targetFile = File(targetParent.uri.toFile(), sourceFile.name)

            return if (sourceFile.absolutePath == targetFile.absolutePath) {
                // 源和目标是同一文件
                this
            } else if (sourceFile.renameTo(targetFile)) {
                // 重命名成功
                DocumentFile.fromFile(targetFile)
            } else {
                // 重命名失败
                null
            }
        }
        ContentResolver.SCHEME_CONTENT -> {
            // 处理 Content Provider 路径
            if (uri.authority != targetParent.uri.authority) {
                throw NotEfficientlyMovableException("Source authority (${uri.authority}) != " +
                        "target parent authority (${targetParent.uri.authority})")
            } else if (flags and DocumentsContract.Document.FLAG_SUPPORTS_MOVE == 0) {
                throw NotEfficientlyMovableException("File does not advertise move flag")
            } else if (parentFile == null) {
                throw NotEfficientlyMovableException("File does not have known parent")
            }

            if (parentFile!!.uri == targetParent.uri) {
                // 已经在目标目录中
                return this
            }

            return try {
                // 使用 DocumentsContract.moveDocument 执行移动操作
                val targetUri = DocumentsContract.moveDocument(
                    context!!.contentResolver,
                    uri,
                    parentFile!!.uri,
                    targetParent.uri,
                )

                targetUri?.let { DocumentFile.fromTreeUri(context!!, targetUri) }
            } catch (e: Exception) {
                null
            }
        }
        else -> throw IllegalArgumentException("Unsupported scheme: ${uri.scheme}")
    }
}

/**
 * 检查目录是否为空
 * 
 * @return 如果目录为空则返回 true，否则返回 false
 */
private fun DocumentFile.isEmpty(): Boolean {
    require(isDirectory) { "Not a directory" }

    return if (isTree) {
        // 对树形 URI 使用优化的查询方法
        withChildrenWithColumns(emptyArray()) { _, sequence ->
            sequence.none()
        }
    } else {
        // 对普通目录使用标准方法
        listFiles().isEmpty()
    }
}

/**
 * 如果此文档是空目录则删除它
 *
 * 这受 TOCTTOU（检查时到使用时）问题的影响，因为 SAF 没有非递归删除函数
 * 
 * @return 如果删除成功返回 true，否则返回 false
 */
fun DocumentFile.deleteIfEmptyDir(): Boolean {
    if (isDirectory && isEmpty()) {
        return delete()
    }

    return false
}

/**
 * 递归删除空目录
 * 
 * 从当前目录开始向上遍历，删除所有空的父目录
 */
fun DocumentFile.deleteIfEmptyDirRecursively() {
    var current: DocumentFile? = this

    while (current != null) {
        if (!current.deleteIfEmptyDir()) {
            // 如果无法删除（不是空目录），则停止
            return
        }
        // 继续处理父目录
        current = current.parentFile
    }
}